--[[

	Gruppenmitglieder:
		Liang Shi			(0352776)
		Bogdan Ciobotaru	(337005)
		Eik Tauber			(301761)

  ]]


--[[
function var_dump(var)
	if type(var) == 'table' then
		for k,v in pairs(var) do print (k .. " => " .. tostring(v)); end
	else
		print(tostring(var));
	end
end
]]

_GLOBAL_MT = {__index =	function(t,k)
							if k == 'Class' then
								setmetatable(_G, {__index = function(t,k) if k=='Class' then error('Error: Class is a keyword'); end; _G[k]={_created_by='_GLOBAL_MT', _name_of_var=k}; return _G[k]; end});
								return _createClass;
							end
						end
			}

String	= 'string';
Number	= 'number';
Boolean	= 'boolean';

function _isClass (t)
	return		type(t)			== 'table'
			and	type(t._name)	== 'string'
			and	_G[t._name]		== t
end


--[[
	checks whether c2 is a sub class of c1
  ]]
function _isSubClassOf(c1, c2)
	-- the trivial case
	if c1 == c2 then return true; end

	-- there is no further super class
	if c2._super == nil then return false; end

	-- recursively test the same with the super class of c2
	return _isSubClassOf(c1, c2._super);
end

--[[
	Checks if the types of value is matching the declaration.
	In case value is an Object, we also check if its class is conform or equal to the declaration
  ]]
function _typeMatch(declaration,value)

	-- everything can be nil!
	if value == nil then return true; end

--[[
	if		type(value) == String
		or	type(value) == Number
		or	type(value) == Boolean
	then
		return declaration == type(value);
	end
]]

	if	type(value) == declaration then
		return true;
	end

	if		type(value) == 'table'
		and	_isClass(declaration)
		and	_isClass(value._class)
		and	_isSubClassOf(declaration, value._class)
	then
		return true;
	end


	return false;

end


function _createClass(params)
	if		params[1] == nil			-- do we have a first parameter? (name of the class we want to create)
		or	type(params[1]) ~= String	-- and is it a string? We could also check, if the string is conform to names of global variables in LUA.
	then
		error("Error: expected class name as first parameter");
	end

	local newClass = {};

	-- check if class is already declared. This happens if there is one or more attribute with this class as type.
	if	_G[params[1]] ~= nil then
		if		type(_G[params[1]]) == 'table'
			and	_G[params[1]]['_created_by'] == '_GLOBAL_MT'	-- _created_by is a hidden key and has to be '_GLOBAL_MT' to implicate that this value
																-- is automatically created before call and not by any user
		then
			newClass				= _G[params[1]];
			newClass._created_by	= nil;
			newClass._name_of_var	= nil;
		else
			error('Error: cannot redeclare "' .. params[1] .. '"');
		end
	else
		_G[params[1]] = newClass;
	end

	newClass._name			= params[1];
	newClass._attributes	= {};
	newClass._methods		= {};

	if	params[2] ~= nil then		-- if set, the second parameter is the SuperClass
		if	not _isClass(params[2]) then
			error ("Error: second parameter is not a valid Class");
		end

		-- TODO: check for cycles (is this even necessary? We always check for existing classes, so it shouldn't be possible to create cycles)

		newClass._super = params[2];
		setmetatable(newClass._attributes	, {__index = newClass._super._attributes});
		setmetatable(newClass._methods		, {__index = newClass._super._methods});
	end


	for k,v in pairs(params) do
		if type(k) == 'string' then
			if		newClass._attributes[k]	~= nil
			then
				error('Error: can\'t redeclare property "' .. k .. '"!');
			end

			if		v == String
				or	v == Number
				or	v == Boolean
				or	_isClass(v)
			then
				newClass._attributes[k] = v;
			else
				if		type(v)			== 'table'
					and v._name_of_var	~= nil
				then
					error('Error: unknown type "' .. v._name_of_var .. '"!');
				else
					error('Error: unknown type for "' .. k .. '"!');
				end
			end
		end
	end

	function newClass:_defaultConstructor(...)
		local newObject =	{	_class			= self
							,	_attributes		= {}
							,	_wasDeclared	= {}
							}

		local mt = {};
		mt.__index = function(t,k)
			if		t._class._attributes[k]	== nil
				and	t._class._methods[k]	== nil
			then
				error('Error: unknown property "' .. k ..'"!');
			end

			if t._class._attributes[k] ~= nil
			then
				if t._attributes[k] == nil
				then
					if t._wasDeclared[k] then
						return nil;
					else
						if t._class._attributes[k] == String then
							return "";
						end
						if t._class._attributes[k] == Boolean then
							return false;
						end
						if t._class._attributes[k] == Number then
							return 0;
						end
						return nil;
					end
				else
					return t._attributes[k];
				end
			else	-- the requested property is a function
				return t._class._methods[k];
			end
		end

		mt.__newindex = function(t,k,v)
			if type(v) == 'function' then
				error('Error: cannot declare functions on an object!');
			end

			if t._class._attributes[k] == nil then
				error('Error: unknown property "' .. k ..'"!');
			end

			if not _typeMatch(t._class._attributes[k], v) then
				error('Error: attachment does not match to the declaration of "' .. k .. '"!');
			end

			t._wasDeclared[k]	= true;
			t._attributes[k]	= v;

		end

		setmetatable(newObject, mt);
		if self._methods._userDefinedCreate ~= nil then
			newObject:_userDefinedCreate(...);
		end

		return newObject;
	end


	local mt = {};

	mt.__index = function(t,k)
		if k == 'create' then
			return t._defaultConstructor;
		end
	end

	mt.__newindex = function(t,k,v)
		if type(v) ~= 'function' then
			error('Error: cannot declare attributes on existing classes!');
		end

		if rawget(t._methods,k) ~= nil then
			error('Error: method "' .. k ..'" is already defined!');
		end

		if k == 'create' then
			t._methods._userDefinedCreate = v;
		else
			t._methods[k] = v;
		end
	end


	setmetatable(newClass, mt);

	setmetatable(_G, _GLOBAL_MT);

end


setmetatable(_G, _GLOBAL_MT);

--[[

Class{'Person'-- , SuperClass
	, name		= String
	, age		= Number
	, friend	= Person
}

Class{'Student' , Person
	, matr		= String
}

person	= Person:_defaultConstructor();
student	= Student:_defaultConstructor();


print(person.name);
print(person.age);
person.name	= nil;
person.age	= nil;
print(person.name);
print(person.age);
person.name = "Eik";
person.age = 29;
print(person.name);
print(person.age);


person.friend	= student;
student.name	= "Heinz";
student.matr	= "301761";

print(person.friend.name);
]]
